﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;

namespace WPFEdit.Util
{
    public static class Reflection
    {
		/// <summary>
		/// Get concrete tyeps in the assembly such that T can be assigned from them.
		/// </summary>
		/// <typeparam name="T">The type to get subclasses from</typeparam>
		/// <param name="assembly">The assembly to search</param>
		/// <returns>A collection of types that are subclasses of T, or implement T.</returns>
        public static IEnumerable<Type> GetSubclasses <T> (Assembly assembly)
        {
            foreach (Type t in assembly.GetTypes().Where(type => typeof(T).IsAssignableFrom(type) && !type.IsInterface && !type.IsAbstract))
            {
                yield return t;
            }
        }

		/// <summary>
		/// Gets subclasses of a type, including instances of a supplied custom attribute.
		/// </summary>
		/// <typeparam name="T"></typeparam>
		/// <param name="assembly">The assembly to search</param>
		/// <param name="attributeType">The attribute type to search for</param>
		/// <returns>Pairings of Types with it's instances of the supplied attribute. Throws an exception if any of the types have no instances of the supplied attribute.</returns>
        public static IEnumerable<Tuple<Type, object[]>> GetSubclassesWithAttributes<T>(Assembly assembly, Type attributeType)
        {
            foreach (Type t in GetSubclasses<T>(assembly))
            {
                object[] attr = t.GetCustomAttributes(attributeType, false);
                if (attr == null || attr.Length == 0)
                {
                    throw new Exception(String.Format("Type {0} does not have attributes of type {1}", typeof(T).Name, attributeType.Name));
                }
                yield return new Tuple<Type, object[]>(t, attr);
            }
        }

		/// <summary>
		/// Gets subclasses of a type, including instances of a supplied custom attribute, and the types constructor.
		/// </summary>
		/// <typeparam name="T"></typeparam>
		/// <param name="assembly">The assembly to search</param>
		/// <param name="attributeType">The attribute type to search for</param>
		/// <returns>Triplets of Types with it's instances of the supplied attribute, and the type's no-argument constructor. Throws
		/// an exception if any types do not have a no-arg constructor, or have no instances of the supplied attribute.</returns>
        public static IEnumerable<Tuple<Type, object[], ConstructorInfo>> GetSubclassesWithAttributesAndConstructor<T>(Assembly assembly, Type attributeType)
        {
            foreach (Tuple<Type, object[]> t in GetSubclassesWithAttributes<T>(assembly,attributeType))
            {
                ConstructorInfo cons = t.Item1.GetConstructor(System.Type.EmptyTypes);
                if (cons == null)
                {
                    throw new Exception(String.Format("Type {0} does not have a no-argument constructor", typeof(T).Name));
                }
                yield return new Tuple<Type, object[], ConstructorInfo>(t.Item1, t.Item2, cons);
            }
        }

        private static object[] s_emptyObjects = new object[0];

        public static object[] EmptyObjects
        {
            get { return Reflection.s_emptyObjects; }
        }

		/// <summary>
		/// Gets all properties of an object with the supplied Property Type
		/// </summary>
		/// <param name="obj">The object to search</param>
		/// <param name="propertyType">The Type to search</param>
		/// <returns>A list of properties of the supplied property type</returns>
        public static IEnumerable<PropertyInfo> GetAllPropertiesOfType(object obj, Type propertyType)
        {
            Type type = obj.GetType();
            PropertyInfo[] props = type.GetProperties(BindingFlags.Public | BindingFlags.Instance);
            return props.Where<PropertyInfo>(prop => prop.PropertyType == propertyType);
        }

		/// <summary>
		/// Creates an instance of a type using its default constructor. Throws an exception if there is no default constructor for
		/// the type.
		/// </summary>
		/// <param name="t"></param>
		/// <returns></returns>
		///
		public static object ConstructFrom(Type t)
		{
			ConstructorInfo cons = t.GetConstructor(Type.EmptyTypes);
			if (cons == null) throw new InvalidOperationException("Type has no default constructor:" + t.Name);
			return cons.Invoke(EmptyObjects);
		}
    }
}
